#ifndef __HOOK_HELPER_H__
#define __HOOK_HELPER_H__

#include <assert.h>
#include <poll.h>
#include <map>
#include "fd_context.h"
#include "../co.h"
#include "../co_utils.h"

class BlockingGuard : public LogAdapterCo<BlockingGuard>
{
public:
    explicit BlockingGuard(const CoFdContextPtr& ptr);
    ~BlockingGuard();

private:
    CoFdContextPtr _ptr;
};

class FdContextMgr : public LogAdapterCo<FdContextMgr>
{
public:
    bool add(int fd, CoFdContextPtr ptr);

    bool del(int fd);

    CoFdContextPtr get(int fd);

    bool exists(int fd);
   
private:
    std::mutex _mutex;
    std::map<int, CoFdContextPtr> _fd_ctxs;
};

class HookRealize : public LogAdapterCo<HookRealize>
{
public:
    HookRealize();

    void init();

    int socket(int domain, int type, int protocol);
    int close(int fd);
    int connect(int fd, const struct sockaddr* addr, socklen_t addrlen);
    int accept(int fd, struct sockaddr* addr, socklen_t* addrlen);
    int read(int fd, void* buf, size_t len);
    int write(int fd, const void* buf, size_t len);
    int send(int fd, const void* buf, size_t len, int flags);
    int recv(int fd, void* buf, size_t len, int flags);
    int poll(struct pollfd* fds, nfds_t nfds, int timeout);
    unsigned int sleep(unsigned int seconds);
    int usleep(useconds_t usec, bool sleep_call = false);
    int getsockopt(int fd, int level, int optname, void* optval, socklen_t* optlen);
    int setsockopt(int fd, int level, int optname, const void* optval, socklen_t optlen);
    int fcntl(int fd, int cmd);
    int fcntl(int fd, int cmd, int arg);
    int fcntl(int fd, int cmd, struct flock* arg);
    int fcntl(int fd, int cmd, struct f_owner_exlock* arg);
    int pipe(int pipefd[2]);

private:
    template <typename F, typename... Args>
    ssize_t read_write_common(
        int fd,
        const char* fname,
        int event,
        CoFdTimeoutType timeout_type,
        F fn,
        Args&&... args
    )
    {
        LOG_DEBUG("call function:%s, fd:%d", fname, fd);

        auto ctx = g_manager.fd_context_mgr_ptr()->get(fd);
        if (!ctx) {
            LOG_ERROR("get ctx fail, fd:%d", fd);
        }
        assert(ctx);

        if (!is_in_co_env()) {
            if (ctx->is_block()) {
                BlockingGuard guard(ctx);
                return fn(fd, forward<Args>(args)...);
            } else {
                return fn(fd, forward<Args>(args)...);
            }
        }

        struct pollfd fds[1];
        fds[0].fd = fd;
        fds[0].events  = event;
        fds[0].revents = 0;

        auto f_poll = std::bind(
            &HookRealize::poll,
            this,
            std::placeholders::_1,
            std::placeholders::_2,
            std::placeholders::_3
        );
        
        LOG_DEBUG(
            "call function:%s, fd:%d, poll, timeout:%d",
            fname,
            fd,
            ctx->get_timeout(timeout_type)
        );
        int ret = CommonUtils::call_without_intr(
            f_poll,
            fds,
            1,
            ctx->get_timeout(timeout_type)
        );
        LOG_DEBUG(
            "poll, fd:%d, timeout:%d, ret:%d",
            fd,
            ctx->get_timeout(timeout_type),
            ret
        );
        if (ret == 0) {
            errno = EAGAIN;
            return -1;
        } else if (ret == -1) {
            return -1;
        }
        return CommonUtils::call_without_intr(fn, fd, forward<Args>(args)...);
    }
};

#endif

